package rasterprimate;

import java.io.BufferedWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;

//import org.apache.xerces.util.URI;
import org.geotools.referencing.GeodeticCalculator;
import com.vividsolutions.jts.algorithm.ConvexHull;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateList;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Location;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

import repast.simphony.context.Context;
import repast.simphony.context.space.gis.GeographyFactoryFinder;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.parameter.Parameters;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.gis.Geography;
import repast.simphony.space.gis.GeographyParameters;
import repast.simphony.space.gis.ShapefileLoader;
import repast.simphony.space.gis.ShapefileWriter;
import utils.GroupUtils;

import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;
import jxl.write.Number;

// this is an Object used to record the progression of the simulation
public class Watcher {

	//context specific variables
	Context context;
	Geography geog;
	final RunEnvironment runEnvironment;
	Parameters p;

	Coordinate[] routeCoords;
	CoordinateList routeCoordsList;
	ArrayList<MarkerPoint> routeCoordsListFinal;

	ArrayList <Double> stepLength,turnAngle,stepNetLength,groupSpread,homeRange,dayDistance,subDayDistance;
	ArrayList<Primate> primateGroup;
	ArrayList<PrimateTopo> primateGroupTopo;

	//marker variables
	int markerId = 0;
	MarkerPoint previousMP;
	MarkerPoint currentMP;
	MarkerPoint day_start_mp,day_end_mp;
	double distanceTraveled=0,subDayDistanceTraveled=0;
	int ticksSubDay=0,sub=14,ticksCount=0;//sub is the number of ticks to sample from a day. Used to match observed data (e.g., 14 = 7 hours)
	
	//output
	WritableSheet excelSheet;
	WritableWorkbook workbook;
	
	public Watcher(Context c,Geography geography, ArrayList<Primate> a,int numbGroups ){

		//get/set parameters
		runEnvironment = RunEnvironment.getInstance();
		context = c;
		geog = geography;
		stepLength=new ArrayList();
		turnAngle=new ArrayList();
		stepNetLength=new ArrayList();
		homeRange=new ArrayList();
		groupSpread=new ArrayList();
		dayDistance=new ArrayList();
		subDayDistance=new ArrayList();
		p = RunEnvironment.getInstance().getParameters();

		previousMP = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);
		currentMP = new MarkerPoint(geog,new Coordinate(0,0),-1,-9999,-9999);
		day_start_mp = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);
		day_end_mp = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);

		//remember all individuals
		primateGroup = a;

		routeCoords = new Coordinate[26];
		routeCoordsList = new CoordinateList();
		routeCoordsListFinal = new CoordinateList();
		
		/*
		 * creating a file to store the output of the counts
		 */

		try{
		File fileOut = new File("misc/aug15/MovementStats_final_"+Constants.topoMem+"_"+Constants.memRetention+"_rand"+p.getValueAsString("randomSeed")+".xls");
		WorkbookSettings wbSettings = new WorkbookSettings();
		wbSettings.setLocale(new Locale("en", "EN"));

		workbook = Workbook.createWorkbook(fileOut, wbSettings);
		workbook.createSheet("BBDCount", 0);
		excelSheet = workbook.getSheet(0);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public Watcher (Context c,Geography geography, ArrayList<PrimateTopo> a,int numbGroups, int nothing ){

		//get/set parameters
		runEnvironment = RunEnvironment.getInstance();
		context = c;
		geog = geography;
		stepLength=new ArrayList();
		turnAngle=new ArrayList();
		stepNetLength=new ArrayList();
		homeRange=new ArrayList();
		groupSpread=new ArrayList();
		dayDistance=new ArrayList();
		subDayDistance=new ArrayList();
		p = RunEnvironment.getInstance().getParameters();

		previousMP = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);
		currentMP = new MarkerPoint(geog,new Coordinate(0,0),-1,-9999,-9999);
		day_start_mp = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);
		day_end_mp = new MarkerPoint(geog,new Coordinate(0,0),-1,0,0);


		//remember all individuals
		primateGroupTopo = a;

		routeCoords = new Coordinate[26];
		routeCoordsList = new CoordinateList();
		routeCoordsListFinal = new CoordinateList();
		
		/*
		 * creating a file to store the output of the counts
		 */
		try{
			File fileOut = new File("MovementStats_final_"+Constants.topoMem+"_"+Constants.memRetention+"_rand"+p.getValueAsString("randomSeed")+".xls");
			WorkbookSettings wbSettings = new WorkbookSettings();
			wbSettings.setLocale(new Locale("en", "EN"));

			workbook = Workbook.createWorkbook(fileOut, wbSettings);
			workbook.createSheet("Movement", 0);
			excelSheet = workbook.getSheet(0);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//Procedures to run per tick (1/2 hour)
	@ScheduledMethod(start=2, interval = 1, priority = 0)
	public void stepCharacteristics(){

		recordFission();
		double dist = recordStepLength();
		distanceTraveled = distanceTraveled + dist;
		if (ticksSubDay< sub){
			subDayDistanceTraveled = subDayDistanceTraveled + dist;
			if (ticksCount < ticksSubDay){
				this.getCurrentMarkerPoint().setRoute(1);
				ticksCount++;
			}
		}
		ticksSubDay++;
		recordTurnAngle();
		if (Constants.topoMem==false){
			recordGroupSpread(primateGroup);
		} else {
			recordGroupSpread(primateGroupTopo,1);
		}
	}
	
	//Procedures to run per day
	@ScheduledMethod(start=2, interval = 26, priority = 0)
	public void DailyStepCharacteristics(){
	recordStepNetLength();
	dayDistance.add(distanceTraveled);
	subDayDistance.add(subDayDistanceTraveled);
	distanceTraveled = 0;
	subDayDistanceTraveled=0;
	ticksSubDay=0;
	ticksCount=0;
	}

	//Procedures to run per month
	@ScheduledMethod(start=2, interval = 26*30, priority = 0)
	public void MonthStepCharacteristics() throws RowsExceededException, WriteException{
		//tasks to take care of every month: after three month (90 days) memory building time
		if (RunEnvironment.getInstance().getCurrentSchedule().getTickCount()> 26 * 30){

			calculateHomeRangeSize();

			//count all points within 10x10 grid cells
			//exportCountPointsBBD();

			//re-initialize the collection of marker points for next months calculations
			routeCoordsListFinal.removeAll(routeCoordsListFinal);

		}

		//Procedures to run at the end of the simulation
		if(RunEnvironment.getInstance().getCurrentSchedule().getTickCount()>=26*(Integer)p.getValue("days")){//2339

			//output the point counts by grid square (30x30m)
			//exportCountPoints();

			exportMarkerPoints();
			recordData();
			//exportGridCounts();

		}
	}

	/****************************************data collection methods *******************************************/

	private double recordStepLength(){

		double stepLengthCal = previousMP.getCoord().distance(getCurrentMarkerPoint().getCoord());
		stepLength.add(stepLengthCal);

		return stepLengthCal;
	}

	private double recordTurnAngle(){

		//calculate angle at which to move
		double adjasent = (double)(getCurrentMarkerPoint().getCoord().x-previousMP.getCoord().x);
		double opposite = (double)(getCurrentMarkerPoint().getCoord().y-previousMP.getCoord().y);
		double angle=0;

		angle = Math.atan(Math.abs(opposite/adjasent));
		if (adjasent<0) angle=(Math.PI)-angle;
		if (opposite<0) angle=2*(Math.PI)-angle;

		turnAngle.add(angle);

		return angle;
	}
	
	private void recordStepNetLength(){
		
		day_end_mp = currentMP;
		double stepLengthNet = day_end_mp.getCoord().distance(day_start_mp.getCoord());
		stepNetLength.add(stepLengthNet);
		day_start_mp = day_end_mp;
	}
	
	private void recordGroupSpread(ArrayList<Primate> groupMates){
		int count=0;
		Coordinate coordList[] = new Coordinate[groupMates.size()];

		for(Primate p: groupMates){
			coordList[count]=p.getCoord();
			count++;
		}
		
		ConvexHull convexHull = new ConvexHull(coordList,new GeometryFactory());
		double homeArea = convexHull.getConvexHull().getArea();
		
		groupSpread.add(homeArea);
	}

	private void recordGroupSpread(ArrayList<PrimateTopo> groupMates, int i){
		int count=0;
		Coordinate coordList[] = new Coordinate[groupMates.size()];

		for(PrimateTopo p: groupMates){
			coordList[count]=p.getCoord();
			count++;
		}
		
		ConvexHull convexHull = new ConvexHull(coordList,new GeometryFactory());
		double homeArea = convexHull.getConvexHull().getArea();
		
		groupSpread.add(homeArea);
	}
	
	private void recordFission(){

		Coordinate finalCenterC = null;

		if (Constants.topoMem==true){

			ArrayList<PrimateTopo> primatesTopo = primateGroupTopo;

			boolean containsEnough =  false;
			Coordinate coord =null;

			while (containsEnough == false){
				//calculate groups center
				coord = GroupUtils.getGroupCenter(primatesTopo,0);

				//test group center accuracy: does group center contain >80% of individuals
				containsEnough = GroupUtils.getGroupCenterThreshold(coord, this, primatesTopo.size(), 0);

				if (containsEnough==false){
					double dist = 0;
					PrimateTopo furthestP = null;
					//remove furthest individual
					for (PrimateTopo pt : primatesTopo){
						if (coord.distance(pt.getCoord())>dist){
							dist=coord.distance(pt.getCoord());
							furthestP = pt;
						}
					}
					primatesTopo.remove(furthestP);

					if (primatesTopo.size()==0){
						System.out.println("No group center meets threshold requirments");
						containsEnough = true;
					}
				}
			}

			finalCenterC = coord;

		}else{

			ArrayList<Primate> primates = primateGroup;

			boolean containsEnough =  false;
			Coordinate coord = null;

			int count=0;

			while (containsEnough == false){
				//calculate groups center
				coord = GroupUtils.getGroupCenter(primates);

				//test group center accuracy: e.g. does group center contain >80% of individuals within 100 meters
				containsEnough = GroupUtils.getGroupCenterThreshold(coord, this, primates.size());

				if (containsEnough==false){
					double dist = 0;
					Primate furthestP = null;
					//remove furthest individual
					for (Primate p : primates){
						if (coord.distance(p.getCoord())>dist){
							dist=coord.distance(p.getCoord());
							furthestP = p;
						}
					}
					primates.remove(furthestP);

					if (primates.size()<=1){
						System.out.println("No group center meets threshold requirments");
						containsEnough = true;
					}
				}
			}

			finalCenterC = coord;

		}

		MarkerPoint mp = new MarkerPoint(geog,finalCenterC,markerId,this.recordStepLength(),this.recordTurnAngle());
		markerId++;
		setCurrentMarkerPoint(mp);
		mp.createPoint();
		context.add(mp);
		routeCoordsListFinal.add(mp);
	}

	//calculate monthly home range size
	private void calculateHomeRangeSize(){

		double homeArea = 0;
		int count=0;
		GeometryFactory fac = new GeometryFactory();
		Coordinate[] coordList = new Coordinate[routeCoordsListFinal.size()];

		for(MarkerPoint mp: routeCoordsListFinal){
			coordList[count]=mp.getCoord();
			count++;
		}

		ConvexHull convexHull = new ConvexHull(coordList,new GeometryFactory());
		homeArea = convexHull.getConvexHull().getArea();
		homeRange.add(homeArea);
		//System.out.println("convex " + homeArea);
	}

	/*private void exportCountPointsBBD(){

		int count = 0;

		//record which month is being calculated
		try {
			outputStream.append("Month "+(int)RunEnvironment.getInstance().getCurrentSchedule().getTickCount()/(26*30));
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		//count the cells with points (only recording presence or absence data)
		for (int i = 0;i<10;i++){ //x cell (0 -> 4: 5 in total)
			for (int j = 0;j<10;j++){ //y cell (0 -> 4: 5 in total)
				count=0;

				Iterable<Resource> resCount =  context.getAgentLayer(Resource.class);
				for (Resource r : resCount){ //loop through all resources and add up the count

					if (r.getBBcellX()==i&&r.getBBcelly()==j){
						System.out.println("in cell "+ i + " " + j);
						if (r.getPointCount()>0){
							System.out.println("there is one or more points in this cell ++ to count");
							count++;
						}
					}
				}

				//add the count to the outputstream
				try { 
					outputStream.append("cell x "+i+" y "+j+ " count "+ count);
					outputStream.newLine();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}*/

	//create shapefiles to be analised on a GIS
	private void exportGridCounts(){
		System.out.println("Exporting Shapefiles to be analyzed");

		String mem = RunEnvironment.getInstance().getParameters().getValueAsString("weightOfMem");
		String rand = RunEnvironment.getInstance().getParameters().getValueAsString("randomSeed"); 
		File res = new File("misc/res"+mem+"_"+rand+".shp");

		ShapefileWriter shapeOut = new ShapefileWriter(geog);
		try {
			shapeOut.write(geog.getLayer(Resource.class).getName(), res.toURI().toURL());
		} catch (MalformedURLException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NullPointerException n){
			n.printStackTrace();
		}
	}

	private void exportMarkerPoints(){
		System.out.println("Exporting marker points to be analyzed");

		String rand = RunEnvironment.getInstance().getParameters().getValueAsString("randomSeed"); 
		File point = new File("misc/MarkerPoints_"+Constants.topoMem+"_"+Constants.memRetention+"_"+rand+".shp");

		ShapefileWriter shapeOut = new ShapefileWriter(geog);
		try {
			shapeOut.write(geog.getLayer(MarkerPoint.class).getName(), point.toURI().toURL());
		} catch (MalformedURLException e ) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NullPointerException n){
			n.printStackTrace();
		}
	}



	/**************************************** Record and end simulation 
	 * @throws WriteException 
	 * @throws RowsExceededException *******************************************/

	public void recordData() throws RowsExceededException, WriteException {
		System.out.println("finalizing the simuation");
		int col = 0,row=2;
		
		
		String title = "Step Length";
		Label label;
		label = new Label(col, 1,title);
		row++;
		excelSheet.addCell(label);
		for (Double d: stepLength){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title2 = "turn angle";
		Label label2;
		label2 = new Label(col, 1,title2);
		row++;
		excelSheet.addCell(label2);
		for (Double d: turnAngle){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title3 = "group spread";
		Label label3;
		label3 = new Label(col, 1,title3);
		row++;
		excelSheet.addCell(label3);
		for (Double d: groupSpread){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title4 = "Daily step length (net)";
		Label label4;
		label4 = new Label(col, 1,title4);
		row++;
		excelSheet.addCell(label4);
		for (Double d: stepNetLength){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title5 = "Home range";
		Label label5;
		label5 = new Label(col, 1,title5);
		row++;
		excelSheet.addCell(label5);
		for (Double d: homeRange){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title6 = "Daily step length (total)";
		Label label6;
		label6 = new Label(col, 1,title6);
		row++;
		excelSheet.addCell(label6);
		for (Double d: dayDistance){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		col++;
		row=2;
		
		String title7 = "Daily step length (7hours)";
		Label label7;
		label7 = new Label(col, 1,title7);
		row++;
		excelSheet.addCell(label7);
		for (Double d: subDayDistance){
			Number number;
			number = new Number(col,row,d);
			excelSheet.addCell(number);
			row++;
		}
		
		
		try {
			workbook.write();
			workbook.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		

		//end the simulation
		RunEnvironment.getInstance().endAt(this.runEnvironment.getCurrentSchedule().getTickCount());
	}

	public void setCurrentMarkerPoint(MarkerPoint c){
		previousMP = currentMP;
		currentMP  = c;
	}
	private MarkerPoint getCurrentMarkerPoint(){
		return currentMP;
	}

}
